/*
 * Copyright (C) 2018 xuexiangjys(xuexiangjys@163.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.xuexiang.xui_lib.component.textview.badge;

import ohos.agp.components.AttrHelper;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.ComponentParent;
import ohos.agp.components.DependentLayout;
import ohos.agp.components.StackLayout;
import ohos.agp.components.element.Element;
import ohos.agp.render.BlendMode;
import ohos.agp.render.BlurDrawLooper;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.utils.Color;
import ohos.agp.utils.LayoutAlignment;
import ohos.agp.utils.Point;
import ohos.agp.utils.RectFloat;
import ohos.agp.utils.TextAlignment;
import ohos.app.Context;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.TouchEvent;

import com.xuexiang.xui_lib.component.edittext.materialedittext.validation.TextUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * 标记控件，可显示数量和文字的控件
 *
 * @author xuexiang
 * @since 2018-12-28 上午9:08
 */
public class BadgeView extends Component implements Badge, Component.TouchEventListener,
    Component.EstimateSizeListener, Component.DrawTask {
    /**
     * background默认颜色
     */
    public static final int DEFAULT_COLOR_BACKGROUND = 0xFFE84E40;
    /**
     * text默认颜色
     */
    public static final int DEFAULT_COLOR_BADGE_TEXT = 0xFFFFFFFF;
    /**
     * text默认大小
     */
    public static final int DEFAULT_TEXT_SIZE = 11;
    /**
     * 默认间距
     */
    public static final int DEFAULT_BADGE_PADDING = 12;
    /**
     * 默认偏移量
     */
    public static final int DEFAULT_GRAVITY_OFFSET = 1;

    protected Color mColorBackground;
    protected Color mColorBackgroundBorder;
    protected Color mColorBadgeText;
    protected Element mDrawableBackground;
    protected PixelMap mBitmapClip;
    protected boolean mDrawableBackgroundClip;
    protected float mBackgroundBorderWidth;
    protected float mBadgeTextSize;
    protected float mBadgePadding;
    protected int mBadgeNumber;
    protected String mBadgeText;
    protected boolean mDraggable;
    protected boolean mDragging;
    protected boolean mExact;
    protected boolean mShowShadow;
    protected int mBadgeGravity;
    protected float mGravityOffsetX;
    protected float mGravityOffsetY;

    protected float mDefaultRadius;
    protected float mFinalDragDistance;
    protected int mDragQuadrant;
    protected boolean mDragOutOfRange;

    protected RectFloat mBadgeTextRect;
    protected RectFloat mBadgeBackgroundRect;
    protected Path mDragPath;

    protected Paint.FontMetrics mBadgeTextFontMetrics;

    protected Point mBadgeCenter;
    protected Point mDragCenter;
    protected Point mRowBadgeCenter;
    protected Point mControlPoint;

    protected List<Point> mInnerTangentPoints;

    protected Component mTargetView;

    protected int mWidth;
    protected int mHeight;

    protected Paint mBadgeTextPaint;
    protected Paint mBadgeBackgroundPaint;
    protected Paint mBadgeBackgroundBorderPaint;

    protected BadgeAnimator mAnimator;

    protected OnDragStateChangedListener mDragStateChangedListener;

    protected ComponentContainer mActivityRoot;

    /**
     * 构造器
     *
     * @param context
     */
    public BadgeView(Context context) {
        this(context, null);
    }

    private BadgeView(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    private BadgeView(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        /*
         * setLayerType(View.LAYER_TYPE_SOFTWARE, null);
         */

        mBadgeTextRect = new RectFloat();
        mBadgeBackgroundRect = new RectFloat();
        mDragPath = new Path();
        mBadgeCenter = new Point();
        mDragCenter = new Point();
        mRowBadgeCenter = new Point();
        mControlPoint = new Point();
        mInnerTangentPoints = new ArrayList<>();
        mBadgeTextPaint = new Paint();
        mBadgeTextPaint.setAntiAlias(true);
        mBadgeTextPaint.setSubpixelAntiAlias(true);
        mBadgeTextPaint.setFakeBoldText(true);
        mBadgeTextPaint.setBlendMode(BlendMode.SRC_IN);
        mBadgeBackgroundPaint = new Paint();
        mBadgeBackgroundPaint.setAntiAlias(true);
        mBadgeBackgroundPaint.setStyle(Paint.Style.FILL_STYLE);
        mBadgeBackgroundBorderPaint = new Paint();
        mBadgeBackgroundBorderPaint.setAntiAlias(true);
        mBadgeBackgroundBorderPaint.setStyle(Paint.Style.STROKE_STYLE);
        mColorBackground = new Color(DEFAULT_COLOR_BACKGROUND);
        mColorBadgeText = new Color(DEFAULT_COLOR_BADGE_TEXT);
        mColorBackgroundBorder = Color.BLACK;
        mBadgeTextSize = DEFAULT_TEXT_SIZE;
        mBadgePadding = DEFAULT_BADGE_PADDING;
        mBadgeNumber = 0;
        mBadgeGravity = LayoutAlignment.END | LayoutAlignment.TOP;
        mGravityOffsetX = DEFAULT_GRAVITY_OFFSET;
        mGravityOffsetY = DEFAULT_GRAVITY_OFFSET;
        mFinalDragDistance = 90;
        mShowShadow = true;
        mDrawableBackgroundClip = false;

        setEstimateSizeListener(this);
        addDrawTask(this);
        setTouchEventListener(this);
        setBindStateChangedListener(new BindStateChangedListener() {
            @Override
            public void onComponentBoundToWindow(Component component) {
                if (mActivityRoot == null) {
                    findViewRoot(mTargetView);
                }
            }

            @Override
            public void onComponentUnboundFromWindow(Component component) {
            }
        });
    }

    @Override
    public Badge bindTarget(final Component targetView) {
        if (targetView == null) {
            throw new IllegalStateException("targetView can not be null");
        }
        if (getComponentParent() != null) {
            ((ComponentContainer) getComponentParent()).removeComponent(this);
        }
        ComponentParent targetParent = targetView.getComponentParent();

        if (targetParent instanceof ComponentContainer) {
            mTargetView = targetView;
            if (targetParent instanceof BadgeContainer) {
                ((BadgeContainer) targetParent).addComponent(this);
            } else {
                ComponentContainer targetContainer = (ComponentContainer) targetParent;
                int index = targetContainer.getChildIndex(targetView);
                ComponentContainer.LayoutConfig targetParams = targetView.getLayoutConfig();
                targetContainer.removeComponent(targetView);
                final BadgeContainer badgeContainer = new BadgeContainer(getContext());
                if (targetContainer instanceof DependentLayout) {
                    badgeContainer.setId(targetView.getId());
                }
                targetContainer.addComponent(badgeContainer, index, targetParams);
                badgeContainer.addComponent(targetView);
                badgeContainer.addComponent(BadgeView.this);
            }
        } else {
            throw new IllegalStateException("targetView must have a parent");
        }
        return this;
    }

    @Override
    public Component getTargetView() {
        return mTargetView;
    }

    private void findViewRoot(Component view) {
        mActivityRoot = (ComponentContainer) getRootView(view);
        if (mActivityRoot == null) {
            findActivityRoot(view);
        }
    }

    private Component getRootView(Component component) {
        if (component.getComponentParent() != null) {
            return getRootView((Component) component.getComponentParent());
        } else {
            return component;
        }
    }

    private void findActivityRoot(Component view) {
        if (view.getComponentParent() != null && view.getComponentParent() instanceof Component) {
            findActivityRoot((Component) view.getComponentParent());
        } else if (view instanceof ComponentContainer) {
            mActivityRoot = (ComponentContainer) view;
        }
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        switch (event.getAction()) {
            case TouchEvent.OTHER_POINT_DOWN:
            case TouchEvent.PRIMARY_POINT_DOWN:
                float x = event.getPointerPosition(event.getIndex()).getX();
                float y = event.getPointerPosition(event.getIndex()).getY();
                if (mDraggable && event.getPointerId(event.getIndex()) == 0
                    && (x > mBadgeBackgroundRect.left && x < mBadgeBackgroundRect.right
                    && y > mBadgeBackgroundRect.top && y < mBadgeBackgroundRect.bottom)
                    && mBadgeText != null) {
                    initRowBadgeCenter();
                    mDragging = true;
                    updateListener(OnDragStateChangedListener.STATE_START);
                    mDefaultRadius = AttrHelper.vp2px(7, getContext());
                    screenFromWindow(true);
                    mDragCenter.modify(event.getPointerScreenPosition(event.getIndex()).getX(),
                        event.getPointerScreenPosition(event.getIndex()).getY() - (getLocationOnScreen()[1] / 2f));
                    invalidate();
                }
                break;
            case TouchEvent.POINT_MOVE:
                if (mDragging) {
                    mDragCenter.modify(event.getPointerScreenPosition(event.getIndex()).getX(),
                        event.getPointerScreenPosition(event.getIndex()).getY() - getLocationOnScreen()[1]);

                    invalidate();
                }
                break;
            case TouchEvent.OTHER_POINT_UP:
            case TouchEvent.PRIMARY_POINT_UP:
            case TouchEvent.CANCEL:
                if (event.getPointerId(event.getIndex()) == 0 && mDragging) {
                    mDragging = false;
                    onPointerUp();
                }
                break;
            default:
                break;
        }
        return mDragging;
    }

    private void onPointerUp() {
        if (mDragOutOfRange) {
            animateHide(mDragCenter);
            updateListener(OnDragStateChangedListener.STATE_SUCCEED);
        } else {
            reset();
            updateListener(OnDragStateChangedListener.STATE_CANCELED);
        }
    }

    private PixelMap createBadgeBitmap() {
        PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
        options.pixelFormat = PixelFormat.valueOf("ARGB_8888");
        options.size = new Size((int) mBadgeBackgroundRect.getWidth() + AttrHelper.vp2px(3, getContext()),
            (int) mBadgeBackgroundRect.getHeight() + AttrHelper.vp2px(3, getContext()));
        PixelMap bitmap = PixelMap.create(options);
        Canvas canvas = new Canvas();
        drawBadge(canvas, new Point(bitmap.getImageInfo().size.width / 2f,
            bitmap.getImageInfo().size.height / 2f), getBadgeCircleRadius());

        return bitmap;
    }

    protected void screenFromWindow(boolean screen) {
        if (getComponentParent() != null) {
            ((ComponentContainer) getComponentParent()).removeComponent(this);
        }
        if (screen) {
            mActivityRoot.addComponent(this, new StackLayout.LayoutConfig(StackLayout.LayoutConfig.MATCH_PARENT,
                StackLayout.LayoutConfig.MATCH_PARENT));
        } else {
            bindTarget(mTargetView);
        }
    }

    private void showShadowImpl(boolean showShadow) {
        int x = AttrHelper.vp2px(1, getContext());
        int y = AttrHelper.vp2px(1.5f, getContext());
        switch (mDragQuadrant) {
            case 1:
                x = AttrHelper.vp2px(1, getContext());
                y = AttrHelper.vp2px(-1.5f, getContext());
                break;
            case 2:
                x = AttrHelper.vp2px(-1, getContext());
                y = AttrHelper.vp2px(-1.5f, getContext());
                break;
            case 3:
                x = AttrHelper.vp2px(-1, getContext());
                y = AttrHelper.vp2px(1.5f, getContext());
                break;
            case 4:
                x = AttrHelper.vp2px(1, getContext());
                y = AttrHelper.vp2px(1.5f, getContext());
                break;
            default:
                break;
        }

        mBadgeBackgroundPaint.setBlurDrawLooper(new BlurDrawLooper(showShadow ? AttrHelper.vp2px(2, getContext())
            : 0, x, y, new Color(0x33000000)));
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (mAnimator != null && mAnimator.isRunning()) {
            mAnimator.draw(canvas);
            return;
        }
        if (mBadgeText != null) {
            initPaints();
            float badgeRadius = getBadgeCircleRadius();
            float startCircleRadius = mDefaultRadius
                * (1 - MathUtils.getPointDistance(mRowBadgeCenter, mDragCenter) / mFinalDragDistance);
            if (mDraggable && mDragging) {
                mDragQuadrant = MathUtils.getQuadrant(mDragCenter, mRowBadgeCenter);
                showShadowImpl(mShowShadow);
                if (mDragOutOfRange = startCircleRadius < AttrHelper.vp2px(1.5f, getContext())) {
                    updateListener(OnDragStateChangedListener.STATE_DRAGGING_OUT_OF_RANGE);
                    drawBadge(canvas, mDragCenter, badgeRadius);
                } else {
                    updateListener(OnDragStateChangedListener.STATE_DRAGGING);
                    drawDragging(canvas, startCircleRadius, badgeRadius);
                    drawBadge(canvas, mDragCenter, badgeRadius);
                }
            } else {
                findBadgeCenter();
                drawBadge(canvas, mBadgeCenter, badgeRadius);
            }
        }
    }

    @Override
    public boolean onEstimateSize(int widthEstimateConfig, int heightEstimateConfig) {
        int width = EstimateSpec.getSize(widthEstimateConfig);
        int height = EstimateSpec.getSize(heightEstimateConfig);
        mWidth = width;
        mHeight = height;
        setEstimatedSize(EstimateSpec.getChildSizeWithMode(width, width, EstimateSpec.NOT_EXCEED),
            EstimateSpec.getChildSizeWithMode(height, height, EstimateSpec.NOT_EXCEED));

        return true;
    }

    private void initPaints() {
        showShadowImpl(mShowShadow);
        mBadgeBackgroundPaint.setColor(mColorBackground);
        mBadgeBackgroundBorderPaint.setColor(mColorBackgroundBorder);
        mBadgeBackgroundBorderPaint.setStrokeWidth(mBackgroundBorderWidth);
        mBadgeTextPaint.setColor(mColorBadgeText);
        mBadgeTextPaint.setTextAlign(TextAlignment.CENTER);
    }

    private void drawDragging(Canvas canvas, float startRadius, float badgeRadius) {
        float dy = mDragCenter.getPointY() - mRowBadgeCenter.getPointY();
        float dx = mDragCenter.getPointX() - mRowBadgeCenter.getPointX();
        mInnerTangentPoints.clear();
        if (dx != 0) {
            double k1 = dy / dx;
            double k2 = -1 / k1;
            MathUtils.getInnerTangentPoints(mDragCenter, badgeRadius, k2, mInnerTangentPoints);
            MathUtils.getInnerTangentPoints(mRowBadgeCenter, startRadius, k2, mInnerTangentPoints);
        } else {
            MathUtils.getInnerTangentPoints(mDragCenter, badgeRadius, 0d, mInnerTangentPoints);
            MathUtils.getInnerTangentPoints(mRowBadgeCenter, startRadius, 0d, mInnerTangentPoints);
        }
        mDragPath.reset();
        mDragPath.addCircle(mRowBadgeCenter.getPointX(), mRowBadgeCenter.getPointY(), startRadius,
            mDragQuadrant == 1 || mDragQuadrant == 2 ? Path.Direction.COUNTER_CLOCK_WISE : Path.Direction.CLOCK_WISE);
        mControlPoint.modify((mRowBadgeCenter.getPointX() + mDragCenter.getPointX()) / 2.0f,
            (mRowBadgeCenter.getPointY() + mDragCenter.getPointY()) / 2.0f);

        mDragPath.moveTo(mInnerTangentPoints.get(2).getPointX(), mInnerTangentPoints.get(2).getPointY());
        mDragPath.quadTo(mControlPoint.getPointX(), mControlPoint.getPointY(),
            mInnerTangentPoints.get(0).getPointX(), mInnerTangentPoints.get(0).getPointY());
        mDragPath.lineTo(mInnerTangentPoints.get(1).getPointX(), mInnerTangentPoints.get(1).getPointY());
        mDragPath.quadTo(mControlPoint.getPointX(), mControlPoint.getPointY(),
            mInnerTangentPoints.get(3).getPointX(), mInnerTangentPoints.get(3).getPointY());
        mDragPath.lineTo(mInnerTangentPoints.get(2).getPointX(), mInnerTangentPoints.get(2).getPointY());
        mDragPath.close();
        canvas.drawPath(mDragPath, mBadgeBackgroundPaint);

        // draw dragging border
        if (mColorBackgroundBorder != null && mBackgroundBorderWidth > 0) {
            mDragPath.reset();
            mDragPath.moveTo(mInnerTangentPoints.get(2).getPointX(), mInnerTangentPoints.get(2).getPointY());
            mDragPath.quadTo(mControlPoint.getPointX(), mControlPoint.getPointY(),
                mInnerTangentPoints.get(0).getPointX(), mInnerTangentPoints.get(0).getPointY());
            mDragPath.moveTo(mInnerTangentPoints.get(1).getPointX(), mInnerTangentPoints.get(1).getPointY());
            mDragPath.quadTo(mControlPoint.getPointX(), mControlPoint.getPointY(),
                mInnerTangentPoints.get(3).getPointX(), mInnerTangentPoints.get(3).getPointX());
            float startY;
            float startX;
            if (mDragQuadrant == 1 || mDragQuadrant == 2) {
                startX = mInnerTangentPoints.get(2).getPointX() - mRowBadgeCenter.getPointX();
                startY = mRowBadgeCenter.getPointY() - mInnerTangentPoints.get(2).getPointY();
            } else {
                startX = mInnerTangentPoints.get(3).getPointX() - mRowBadgeCenter.getPointX();
                startY = mRowBadgeCenter.getPointY() - mInnerTangentPoints.get(3).getPointY();
            }
            float startAngle = 360 - (float) MathUtils.radianToAngle(MathUtils.getTanRadian(Math.atan(startY / startX),
                mDragQuadrant - 1 == 0 ? 4 : mDragQuadrant - 1));

            mDragPath.addArc(new RectFloat(mRowBadgeCenter.getPointX() - startRadius,
                mRowBadgeCenter.getPointY() - startRadius,
                mRowBadgeCenter.getPointX() + startRadius,
                mRowBadgeCenter.getPointY() + startRadius), startAngle, 180);
            canvas.drawPath(mDragPath, mBadgeBackgroundBorderPaint);
        }
    }

    private void drawBadge(Canvas canvas, Point center, float radius) {
        if (center.getPointX() == -1000 && center.getPointY() == -1000) {
            return;
        }
        if (mBadgeText.isEmpty() || mBadgeText.length() == 1) {
            mBadgeBackgroundRect.left = center.getPointX() - (int) radius;
            mBadgeBackgroundRect.top = center.getPointY() - (int) radius;
            mBadgeBackgroundRect.right = center.getPointX() + (int) radius;
            mBadgeBackgroundRect.bottom = center.getPointY() + (int) radius;

            if (mDrawableBackground != null) {
                drawBadgeBackground(canvas);
            } else {
                canvas.drawCircle(center.getPointX(), center.getPointY(), radius, mBadgeBackgroundPaint);
                if (mColorBackgroundBorder != null && mBackgroundBorderWidth > 0) {
                    canvas.drawCircle(center.getPointX(), center.getPointY(), radius, mBadgeBackgroundBorderPaint);
                }
            }
        } else {
            mBadgeBackgroundRect.left = center.getPointX() - (mBadgeTextRect.getWidth() / 2f + mBadgePadding);
            mBadgeBackgroundRect.top = center.getPointY() - (mBadgeTextRect.getHeight() / 2f + mBadgePadding * 0.5f);
            mBadgeBackgroundRect.right = center.getPointX() + (mBadgeTextRect.getWidth() / 2f + mBadgePadding);
            mBadgeBackgroundRect.bottom = center.getPointY() + (mBadgeTextRect.getHeight() / 2f + mBadgePadding * 0.5f);
            radius = mBadgeBackgroundRect.getHeight() / 2f;
            if (mDrawableBackground != null) {
                drawBadgeBackground(canvas);
            } else {
                canvas.drawRoundRect(mBadgeBackgroundRect, radius, radius, mBadgeBackgroundPaint);
                if (mColorBackgroundBorder != null && mBackgroundBorderWidth > 0) {
                    canvas.drawRoundRect(mBadgeBackgroundRect, radius, radius, mBadgeBackgroundBorderPaint);
                }
            }
        }
        if (!mBadgeText.isEmpty()) {
            canvas.drawText(mBadgeTextPaint, mBadgeText, center.getPointX(),
                (mBadgeBackgroundRect.bottom + mBadgeBackgroundRect.top
                    - mBadgeTextFontMetrics.bottom - mBadgeTextFontMetrics.top) / 2f);
        }
    }

    private void drawBadgeBackground(Canvas canvas) {
        /*
            mBadgeBackgroundPaint.setShadowLayer(0, 0, 0, 0);
         */
        int left = (int) mBadgeBackgroundRect.left;
        int top = (int) mBadgeBackgroundRect.top;
        int right = (int) mBadgeBackgroundRect.right;
        int bottom = (int) mBadgeBackgroundRect.bottom;
        if (mDrawableBackgroundClip) {
            right = left + mBitmapClip.getImageInfo().size.width;
            bottom = top + mBitmapClip.getImageInfo().size.height;
            canvas.saveLayer(new RectFloat(left, top, right, bottom), null);
        }
        mDrawableBackground.setBounds(left, top, right, bottom);
        mDrawableBackground.drawToCanvas(canvas);
        if (mDrawableBackgroundClip) {
            mBadgeBackgroundPaint.setBlendMode(BlendMode.DST_IN);
            canvas.drawPixelMapHolder(new PixelMapHolder(mBitmapClip), left, top, mBadgeBackgroundPaint);
            canvas.restore();
            mBadgeBackgroundPaint.setBlendMode(null);
            if (mBadgeText.isEmpty() || mBadgeText.length() == 1) {
                canvas.drawCircle(mBadgeBackgroundRect.getCenter().getPointX(),
                    mBadgeBackgroundRect.getCenter().getPointY(),
                    mBadgeBackgroundRect.getWidth() / 2f,
                    mBadgeBackgroundBorderPaint);
            } else {
                canvas.drawRoundRect(mBadgeBackgroundRect,
                    mBadgeBackgroundRect.getHeight() / 2, mBadgeBackgroundRect.getHeight() / 2,
                    mBadgeBackgroundBorderPaint);
            }
        } else {
            canvas.drawRect(mBadgeBackgroundRect, mBadgeBackgroundBorderPaint);
        }
    }

    private void createClipLayer() {
        if (mBadgeText == null) {
            return;
        }
        if (!mDrawableBackgroundClip) {
            return;
        }
        if (mBitmapClip != null && !mBitmapClip.isReleased()) {
            mBitmapClip.release();
        }
        float radius = getBadgeCircleRadius();
        PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
        options.pixelFormat = PixelFormat.valueOf("ARGB_4444");
        if (mBadgeText.isEmpty() || mBadgeText.length() == 1) {
            mBitmapClip = PixelMap.create(new int[]{(int) radius * 2, (int) radius * 2}, options);
            Canvas srcCanvas = new Canvas();
            srcCanvas.drawCircle(mBitmapClip.getImageInfo().size.width / 2f,
                mBitmapClip.getImageInfo().size.height / 2f,
                mBitmapClip.getImageInfo().size.width / 2f,
                mBadgeBackgroundPaint);
        } else {
            mBitmapClip = PixelMap.create(new int[]{(int) (mBadgeTextRect.getWidth() + mBadgePadding * 2),
                (int) (mBadgeTextRect.getHeight() + mBadgePadding)}, options);
            Canvas srcCanvas = new Canvas();
            srcCanvas.drawRoundRect(new RectFloat(0, 0,
                    mBitmapClip.getImageInfo().size.width, mBitmapClip.getImageInfo().size.height),
                mBitmapClip.getImageInfo().size.width / 2f,
                mBitmapClip.getImageInfo().size.height / 2f, mBadgeBackgroundPaint);
        }
    }

    private float getBadgeCircleRadius() {
        if (mBadgeText.isEmpty()) {
            return mBadgePadding;
        } else if (mBadgeText.length() == 1) {
            return mBadgeTextRect.getHeight() > mBadgeTextRect.getWidth()
                ? mBadgeTextRect.getHeight() / 2f + mBadgePadding * 0.5f
                : mBadgeTextRect.getWidth() / 2f + mBadgePadding * 0.5f;
        } else {
            return mBadgeBackgroundRect.getHeight() / 2f;
        }
    }

    private void findBadgeCenter() {
        float rectWidth = Math.max(mBadgeTextRect.getHeight(), mBadgeTextRect.getWidth());
        switch (mBadgeGravity) {
            case LayoutAlignment.START | LayoutAlignment.TOP:
                mBadgeCenter.modify(mGravityOffsetX + mBadgePadding + rectWidth / 2f,
                    mGravityOffsetY + mBadgePadding + mBadgeTextRect.getHeight() / 2f);
                break;
            case LayoutAlignment.START | LayoutAlignment.BOTTOM:
                mBadgeCenter.modify(mGravityOffsetX + mBadgePadding + rectWidth / 2f,
                    mHeight - (mGravityOffsetY + mBadgePadding + mBadgeTextRect.getHeight() / 2f));
                break;
            case LayoutAlignment.END | LayoutAlignment.TOP:
                mBadgeCenter.modify(mWidth - (mGravityOffsetX + mBadgePadding + rectWidth / 2f),
                    mGravityOffsetY + mBadgePadding + mBadgeTextRect.getHeight() / 2f);
                break;
            case LayoutAlignment.END | LayoutAlignment.BOTTOM:
                mBadgeCenter.modify(mWidth - (mGravityOffsetX + mBadgePadding + rectWidth / 2f),
                    mHeight - (mGravityOffsetY + mBadgePadding + mBadgeTextRect.getHeight() / 2f));
                break;
            case LayoutAlignment.CENTER:
                mBadgeCenter.modify(mWidth / 2f, mHeight / 2f);
                break;
            case LayoutAlignment.CENTER | LayoutAlignment.TOP:
                mBadgeCenter.modify(mWidth / 2f, mGravityOffsetY + mBadgePadding + mBadgeTextRect.getHeight() / 2f);
                break;
            case LayoutAlignment.CENTER | LayoutAlignment.BOTTOM:
                mBadgeCenter.modify(mWidth / 2f,
                    mHeight - (mGravityOffsetY + mBadgePadding + mBadgeTextRect.getHeight() / 2f));
                break;
            case LayoutAlignment.CENTER | LayoutAlignment.START:
                mBadgeCenter.modify(mGravityOffsetX + mBadgePadding + rectWidth / 2f, mHeight / 2f);
                break;
            case LayoutAlignment.CENTER | LayoutAlignment.END:
                mBadgeCenter.modify(mWidth - (mGravityOffsetX + mBadgePadding + rectWidth / 2f), mHeight / 2f);
                break;
            default:
                break;
        }
        initRowBadgeCenter();
    }

    private void measureText() {
        mBadgeTextRect.left = 0;
        mBadgeTextRect.top = 0;
        if (TextUtils.isEmpty(mBadgeText)) {
            mBadgeTextRect.right = 0;
            mBadgeTextRect.bottom = 0;
        } else {
            mBadgeTextPaint.setTextSize(AttrHelper.fp2px(mBadgeTextSize, getContext()));
            mBadgeTextRect.right = mBadgeTextPaint.measureText(mBadgeText);
            mBadgeTextFontMetrics = mBadgeTextPaint.getFontMetrics();
            mBadgeTextRect.bottom = mBadgeTextFontMetrics.descent - mBadgeTextFontMetrics.ascent;
        }
        createClipLayer();
    }

    private void initRowBadgeCenter() {
        int[] screenPoint = getLocationOnScreen();
        mRowBadgeCenter.modify(mBadgeCenter.getPointX() + screenPoint[0],
            mBadgeCenter.getPointY() + screenPoint[1] - 129);
    }

    protected void initRowBadgeCenter(float x, float y) {
        mRowBadgeCenter.modify(x, y);
    }

    private void animateHide(Point center) {
        if (mBadgeText == null) {
            return;
        }
        if (mAnimator == null || !mAnimator.isRunning()) {
            screenFromWindow(true);
            mAnimator = new BadgeAnimator(createBadgeBitmap(), center, this);
            mAnimator.start();
            setBadgeNumber(0);
        }
    }

    public void reset() {
        mDragCenter.modify(-1000, -1000);
        mDragQuadrant = 4;
        screenFromWindow(false);
        invalidate();
    }

    @Override
    public void hide(boolean animate) {
        if (animate && mActivityRoot != null) {
            initRowBadgeCenter();
            animateHide(mRowBadgeCenter);
        } else {
            setBadgeNumber(0);
        }
    }

    /*
     * @param badgeNumber equal to zero badge will be hidden, less than zero show dot
     */
    @Override
    public Badge setBadgeNumber(int badgeNumber) {
        mBadgeNumber = badgeNumber;
        if (mBadgeNumber < 0) {
            mBadgeText = "";
        } else if (mBadgeNumber > 99) {
            mBadgeText = mExact ? String.valueOf(mBadgeNumber) : "99+";
        } else if (mBadgeNumber > 0) {
            mBadgeText = String.valueOf(mBadgeNumber);
        } else {
            mBadgeText = null;
        }
        measureText();
        invalidate();
        return this;
    }

    @Override
    public int getBadgeNumber() {
        return mBadgeNumber;
    }

    @Override
    public Badge setBadgeText(String badgeText) {
        mBadgeText = badgeText;
        mBadgeNumber = 1;
        measureText();
        invalidate();
        return this;
    }

    @Override
    public String getBadgeText() {
        return mBadgeText;
    }

    @Override
    public Badge setExactMode(boolean isExact) {
        mExact = isExact;
        if (mBadgeNumber > 99) {
            setBadgeNumber(mBadgeNumber);
        }
        return this;
    }

    @Override
    public boolean isExactMode() {
        return mExact;
    }

    @Override
    public Badge setShowShadow(boolean showShadow) {
        mShowShadow = showShadow;
        invalidate();
        return this;
    }

    @Override
    public boolean isShowShadow() {
        return mShowShadow;
    }

    @Override
    public Badge setBadgeBackgroundColor(Color color) {
        mColorBackground = color;
        if (mColorBackground == Color.TRANSPARENT) {
            mBadgeTextPaint.setBlendMode(null);
        } else {
            mBadgeTextPaint.setBlendMode(BlendMode.SRC_IN);
        }
        invalidate();
        return this;
    }

    @Override
    public Badge stroke(Color color, float width) {
        mColorBackgroundBorder = color;
        mBackgroundBorderWidth = width;
        invalidate();
        return this;
    }

    @Override
    public Color getBadgeBackgroundColor() {
        return mColorBackground;
    }

    @Override
    public Badge setBadgeBackground(Element drawable) {
        return setBadgeBackground(drawable, false);
    }

    @Override
    public Badge setBadgeBackground(Element drawable, boolean clip) {
        mDrawableBackgroundClip = clip;
        mDrawableBackground = drawable;
        createClipLayer();
        invalidate();
        return this;
    }

    @Override
    public Element getBadgeBackground() {
        return mDrawableBackground;
    }

    @Override
    public Badge setBadgeTextColor(Color color) {
        mColorBadgeText = color;
        invalidate();
        return this;
    }

    @Override
    public Color getBadgeTextColor() {
        return mColorBadgeText;
    }

    @Override
    public Badge setBadgeTextSize(float size) {
        mBadgeTextSize = size;
        measureText();
        invalidate();
        return this;
    }

    @Override
    public float getBadgeTextSize() {
        return mBadgeTextSize;
    }

    @Override
    public Badge setBadgePadding(float padding) {
        mBadgePadding = padding;
        createClipLayer();
        invalidate();
        return this;
    }

    @Override
    public float getBadgePadding() {
        return mBadgePadding;
    }

    @Override
    public boolean isDraggable() {
        return mDraggable;
    }

    /*
     * @param gravity only support Gravity.START | Gravity.TOP , Gravity.END | Gravity.TOP ,
     * Gravity.START | Gravity.BOTTOM , Gravity.END | Gravity.BOTTOM ,
     * Gravity.CENTER , Gravity.CENTER | Gravity.TOP , Gravity.CENTER | Gravity.BOTTOM ,
     * Gravity.CENTER | Gravity.START , Gravity.CENTER | Gravity.END
     */
    @Override
    public Badge setBadgeGravity(int gravity) {
        if (gravity == (LayoutAlignment.START | LayoutAlignment.TOP)
            || gravity == (LayoutAlignment.END | LayoutAlignment.TOP)
            || gravity == (LayoutAlignment.START | LayoutAlignment.BOTTOM)
            || gravity == (LayoutAlignment.END | LayoutAlignment.BOTTOM)
            || gravity == (LayoutAlignment.CENTER)
            || gravity == (LayoutAlignment.CENTER | LayoutAlignment.TOP)
            || gravity == (LayoutAlignment.CENTER | LayoutAlignment.BOTTOM)
            || gravity == (LayoutAlignment.CENTER | LayoutAlignment.START)
            || gravity == (LayoutAlignment.CENTER | LayoutAlignment.END)) {
            mBadgeGravity = gravity;
            invalidate();
        } else {
            throw new IllegalStateException("only support Gravity.START | Gravity.TOP , Gravity.END | Gravity.TOP , "
                + "Gravity.START | Gravity.BOTTOM , Gravity.END | Gravity.BOTTOM , Gravity.CENTER"
                + " , Gravity.CENTER | Gravity.TOP , Gravity.CENTER | Gravity.BOTTOM ,"
                + "Gravity.CENTER | Gravity.START , Gravity.CENTER | Gravity.END");
        }
        return this;
    }

    @Override
    public int getBadgeGravity() {
        return mBadgeGravity;
    }

    @Override
    public Badge setGravityOffset(float offset) {
        return setGravityOffset(offset, offset);
    }

    @Override
    public Badge setGravityOffset(float offsetX, float offsetY) {
        mGravityOffsetX = offsetX;
        mGravityOffsetY = offsetY;
        invalidate();
        return this;
    }

    @Override
    public float getGravityOffsetX() {
        return mGravityOffsetX;
    }

    @Override
    public float getGravityOffsetY() {
        return mGravityOffsetY;
    }

    private void updateListener(int state) {
        if (mDragStateChangedListener != null) {
            mDragStateChangedListener.onDragStateChanged(state, this, mTargetView);
        }
    }

    @Override
    public Badge setOnDragStateChangedListener(OnDragStateChangedListener listener) {
        mDraggable = listener != null;
        mDragStateChangedListener = listener;
        return this;
    }

    @Override
    public Point getDragCenter() {
        if (mDraggable && mDragging) {
            return mDragCenter;
        }
        return null;
    }

    private static class BadgeContainer extends ComponentContainer {
        /**
         * 构造器
         *
         * @param context
         */
        public BadgeContainer(Context context) {
            super(context);
            initView();
        }

        /**
         * 构造器
         *
         * @param context
         * @param attrSet
         */
        public BadgeContainer(Context context, AttrSet attrSet) {
            super(context, attrSet);
            initView();
        }

        /**
         * 构造器
         *
         * @param context
         * @param attrSet
         * @param styleName
         */
        public BadgeContainer(Context context, AttrSet attrSet, String styleName) {
            super(context, attrSet, styleName);
            initView();
        }

        private void initView() {
            setArrangeListener(new ArrangeListener() {
                @Override
                public boolean onArrange(int i, int i1, int i2, int i3) {
                    for (int j = 0; j < getChildCount(); j++) {
                        Component child = getComponentAt(j);
                        child.arrange(0, 0, child.getEstimatedWidth(), child.getEstimatedHeight());
                    }
                    return true;
                }
            });

            setEstimateSizeListener(new EstimateSizeListener() {
                @Override
                public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
                    Component targetView = null, badgeView = null;
                    for (int j = 0; j < getChildCount(); j++) {
                        Component child = getComponentAt(j);
                        if (!(child instanceof BadgeView)) {
                            targetView = child;
                        } else {
                            badgeView = child;
                        }
                    }

                    if (targetView == null) {
                        BadgeContainer.super.mEstimateSizeListener.onEstimateSize(widthMeasureSpec, heightMeasureSpec);
                    } else {
                        targetView.estimateSize(widthMeasureSpec, heightMeasureSpec);
                        if (badgeView != null) {
                            badgeView.estimateSize(targetView.getEstimatedWidth(), targetView.getEstimatedHeight());
                        }
                        setEstimatedSize(targetView.getEstimatedWidth(), targetView.getEstimatedHeight());
                    }
                    return true;
                }
            });
        }
    }
}
